//
//  NSObject+HQObserverHelper.m
//  MengLiao
//
//  Created by xiuxin on 2022/7/15.
//  Copyright © 2022 ZHBen. All rights reserved.
//
#import "NSObject+KObserverHelper.h"
#import <objc/message.h>
@interface KObserverInfo : NSObject
//防止强引用，不释放
@property(nonatomic, weak) id observer; //观察者

@end

@implementation KObserverInfo

@end

@interface KObserverHelper: NSObject
@property (nonatomic, unsafe_unretained) id target; //被观察者
@property (nonatomic, unsafe_unretained) id observer; //观察者
@property (nonatomic, copy) NSString *keyPath;
@property (nonatomic, weak) KObserverHelper *factor;
@end

@implementation KObserverHelper
- (void)dealloc
{
    if ( _factor )
    {
        [_target removeObserver:_observer forKeyPath:_keyPath];
    }
}
@end

#define KKVOObserverKey  @"KKVOObserverKey"
#define KKVOHashKey      @"KKVOHashKey"

#define KHashKey(observer,keypath) [NSString stringWithFormat:@"%p-%@",observer, keypath]

@implementation NSObject (KObserverHelper)

- (void)k_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context
{
    NSParameterAssert(observer);
    NSParameterAssert(keyPath);
    
    if (observer == nil
        || ![keyPath isKindOfClass:[NSString class]]
        || keyPath.length == 0)
    {
        return;
    }
    
    NSMutableDictionary<NSString *, NSDictionary *> * observeInfo = objc_getAssociatedObject(self, _cmd);
    NSString * hashKey = KHashKey(observer,keyPath);
    if ([observeInfo.allKeys containsObject:hashKey])
    {
        return;
    }
    else
    {
        if (observeInfo == nil)
        {
            observeInfo = [NSMutableDictionary dictionary];
            objc_setAssociatedObject(self, _cmd, observeInfo, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        }
        
        KObserverInfo *info = [KObserverInfo new];
        info.observer = observer;
        NSDictionary * observerDic = @{
                                      KKVOObserverKey : info,
                                      KKVOHashKey : hashKey
                                     };
        [observeInfo setObject:observerDic forKey:hashKey];
        
        [self addObserver:observer forKeyPath:keyPath options:options context:context];
        
        KObserverHelper *helper = [KObserverHelper new];
        KObserverHelper *sub = [KObserverHelper new];
        
        sub.target = helper.target = self;
        sub.observer = helper.observer = observer;
        sub.keyPath = helper.keyPath = keyPath;
        
        helper.factor = sub;
        sub.factor = helper;
        
        objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(hashKey), helper, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        objc_setAssociatedObject(observer, (__bridge const void * _Nonnull)(hashKey), sub, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
}

- (void)k_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(void *)context
{
    NSParameterAssert(observer);
    NSParameterAssert(keyPath);
    
    if (observer == nil
        || ![keyPath isKindOfClass:[NSString class]]
        || keyPath.length == 0)
    {
        return;
    }
    NSMutableDictionary<NSString *, NSDictionary *> * observeInfo = objc_getAssociatedObject(self, @selector(hq_addObserver:forKeyPath:options:context:));
    NSString * hashKey = KHashKey(observer,keyPath);
    NSDictionary * observerDic = observeInfo[hashKey];
    KObserverInfo *info = [observerDic objectForKey:KKVOObserverKey];
    if (info.observer == observer || !info.observer)
    {
        NSString * originHashKey = [observerDic objectForKey:KKVOHashKey];
        KObserverHelper *helper = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(originHashKey));
        KObserverHelper *sub = objc_getAssociatedObject(observer, (__bridge const void * _Nonnull)(originHashKey));
        helper.factor = nil;
        sub.factor = nil;
        
        [self removeObserver:observer forKeyPath:keyPath context:context];
        [observeInfo removeObjectForKey:hashKey];
    }
}

- (BOOL)k_isObservedBy:(NSObject *)observer keyPath:(NSString *)keyPath
{
    NSMutableDictionary<NSString *, NSDictionary *> * observeInfo = objc_getAssociatedObject(self, @selector(hq_addObserver:forKeyPath:options:context:));
    return [observeInfo.allKeys containsObject:KHashKey(observer,keyPath)];
}

@end

